/*
 * Created on 20-dic-2004
 */
package guru.merlin.server;

import guru.merlin.bus.MessagingInfrastructureException;
import guru.merlin.core.ConfigurationEPException;
import guru.merlin.core.EPException;
import guru.merlin.core.NodeConnectionSentry;
import guru.merlin.core.PeerEPException;

import guru.merlin.bus.EnvelopeBus;
import guru.merlin.core.Envelope;
import guru.merlin.core.EnvelopeEPException;
import guru.merlin.core.EnvelopeHandler;
import guru.merlin.core.EnvelopeProcessor;
import guru.merlin.core.InfrastructureEPException;
import guru.merlin.core.Route;
import guru.merlin.core.Router;
import guru.merlin.core.RuntimeConfigurationEPException;

import org.apache.log4j.Logger;


/**
 * @author cobach Antigua clase PostRouter, modificada por santiago ventura el 22/07/2005. cambiar
 *         nombre a EPContainer
 */
public class EPContainer implements EnvelopeHandler{
    public boolean started=false;
    public EnvelopeProcessor ep;
    static Logger logger = Logger.getLogger(EPContainer.class);
    java.util.Calendar calendarControl = null;
    java.util.Calendar calendarTmp = null;
    int minControl;
    int minTmp;
    Router router;
    public Route route;
    EnvelopeBus bus;
    public int attemptsToReconect = 0;
    public int maxAttemptsReconect = 0;
    public long microCycle;
    public String parametersFilePath; //Aadido por Santiago //MOD SANTIAGO
    
    private NodeConnectionSentry ncs;
    public int monitoringRound; //ronda de vigilancia
    
    private EPServer server;
    
    /**
     * Efectua el preprocesamiento, procesamiento y postprocesamiento de objetos envelopes.
     * Implementa la nueva arquitectura de merlin para el control de transacciones
     *
     *
     * La ejecucion del EnvelopeProcessor puede tener los siguientes resultados con sus respectivas
     * "interpretacion" y "reaccion" por parte del Container:
     *
     * <ul>
     * <li>
     * <b>Exito</b>: El Envelope fue procesado con exito; el Container continua el procesamiento.
     * </li>
     * <li>
     * <b>EnvelopeEPException</b>: El Envelope no podrá ser procesado por alguna característica propia
     * del mismo; el Container envía el Mensaje a la cola de error
     * </li>
     * <li>
     * <b>PeerEPException</b>: El EP tiene problemas en la comunicacion con su <i>Peer</i>; el Container
     * reencola el Envelope e intenta la reconexion del nodo.
     * </li>
     * <li>
     * <b>InfrastructureEPException</b>: El EP tiene problemas de operacion debido a su
     * <i>Infraestructura</i>; el Container reencola el Envelope y dormirá un tiempo (milisegundos)
     * esperando que el problema se resuelva.
     *
     * </li>
     * <li>
     * <b>ConfigurationEPException</b>: La configuracion actual del EP no permite procesar mensajes;
     * el Container reencola el Envelope y parará el Nodo para que no continue el procesamiento de
     * Envelopes.
     * </li>
     * <li>
     * <b>RuntimeConfigurationEPException</b>: La configuracion en Runtime del EP en este momento no
     * le permite procesar el Envelope; el Container envía el Mensaje a la cola de error.
     * </li>
     * </ul>
     *
     *
     *
     *
     * @see guru.merlin.core.EnvelopeHandler#handEnvelope(guru.merlin.core.Envelope)
     */
    public Object handEnvelope(Envelope envelope) {
        Object preProcesingActionReference = null;
        Object procesingActionReference = null;
        Object postProcesingActionReference = null;
        
        try {
            
            //Preprocesamiento
            try {
                preProcesingActionReference = router.preProcesingAction(envelope);
            } catch (Throwable t) {
                logger.error(t);
                throw t;
            }
            
            
            //Procesamiento
            try {
                procesingActionReference = ep.handEnvelope(envelope);
            } catch(EnvelopeEPException e){
                //rollback:
                router.preProcesingActionCompensator(preProcesingActionReference);
                
                router.handleError(envelope, e, e.getMessage() );
                
                throw e;
            } catch (PeerEPException e) {
                //rollback:
                router.preProcesingActionCompensator(preProcesingActionReference);
                
                //dormir un rato:
                synchronized(this){
                    Thread.sleep(microCycle);
                }
                
                //reconectar:
                try {
                    ep.closeConnection();
                } catch (Exception e2) {
                    logger.warn("can't close connectio; will try to open as next step: "+e2);
                }
                try {
                    logger.info(this.route.toString()+" try to reconnect");
                    ep.openConnection(parametersFilePath);
                    ep.start();
                } catch (Throwable t) {
                    logger.error(t);
                } finally {
                    bus.handEnvelope(envelope);
                    throw e;
                }
            } catch(InfrastructureEPException e){
                //rollback:
                router.preProcesingActionCompensator(preProcesingActionReference);
                
                bus.handEnvelope(envelope);
                
                throw e;
            } catch (ConfigurationEPException e) {
                //rollback:
                router.preProcesingActionCompensator(preProcesingActionReference);
                server.metaStop(this);
                logger.warn("The node reported a configuration problem. Stoped by the container.",e);
                
                bus.handEnvelope(envelope);
                
                throw e;
            } catch(RuntimeConfigurationEPException e){
                //rollback:
                router.preProcesingActionCompensator(preProcesingActionReference);
                
                router.handleError(envelope, e, e.getMessage());
                
                throw e;
            }
            
            
            //Postprocesamiento
            try {
                postProcesingActionReference = router.postProcesingAction(envelope);
            } catch (Throwable t) {
                router.preProcesingActionCompensator(preProcesingActionReference);
                ep.retrieveEnvelope(procesingActionReference); //Compensamos la accin handEnvelope
                logger.error(t);
                throw t;
            }
            
            try {
                //AQUI VA CUALQUIER COSA DESPUES DEL POSTPROCESING ACTION.
            } catch (Exception e) {
                router.preProcesingActionCompensator(preProcesingActionReference);
                ep.retrieveEnvelope(procesingActionReference); //Compensamos la accin handEnvelope
                router.postProcesingActionCompensator(postProcesingActionReference);
                logger.error(e);
                throw e;
            }
            
            //AQUI VAN TODOS LOS COMMITS DE SEGUNDA FASE.
            router.commitPreProcesingAction(preProcesingActionReference);
            ep.commitHandEnvelope(procesingActionReference);
            router.commitPostProcesingAction(postProcesingActionReference);
            
            
        } catch (Throwable t) {
            if (envelope != null) {
                StringBuffer sb = new StringBuffer();
                StackTraceElement stt[] = t.getStackTrace();
                sb.append("Se genero un: " + t + "\n");
                sb.append("Stacktrace:\n");
                for (int d = 0; d < stt.length; d++) {
                    sb.append(stt[d].toString() + "\n");
                }                
                logger.error(sb.toString());
            } else {
                logger.error("Envelope was null, eliminated of the queue.");
            }
            t.printStackTrace ();
        } finally{
            callGarbageCollector();
        }
        
        return envelope; //OJO: que hace esto?
    }
    
    //#######################################################################
    
    
    public void start() throws Exception{ //MOD SANTIAGO
        try {
            ep.start();
            bus.start(this.route);
            started = true;
            if (ncs==null || (!ncs.isRunning())) {
                this.startNodeConnectionSentry();
            }
        }catch (Exception e){
            throw new Exception("Can't start: "+e);
        }
    }
    
    public void stop() throws Exception{
        try {
            ep.stop();
            bus.stop(this.route);
            started = false;
        }catch (Exception e){
            throw new Exception("Can't stop: "+e);
        }
    }
    
    public EPContainer(EPServer server, EnvelopeProcessor h, Router r, Route route_, EnvelopeBus bus, String parametersFilePath, int maxAttemptsReconect, long microCycle, int monitoringRound) { //MOD SANTIAGO
        this.server=server;
        this.ep = h;
        router = r;
        this.route = route_;
        this.bus = bus;
        this.parametersFilePath = parametersFilePath;
        this.maxAttemptsReconect = maxAttemptsReconect;
        this.microCycle = microCycle;
        this.monitoringRound = monitoringRound;
    }
    
    /**
     *
     * @see guru.merlin.core.EnvelopeHandler#retrieveEnvelope(java.lang.Object)
     */
    public void retrieveEnvelope(Object EnvelopeReference) throws EPException {
    }
    
    /**
     *
     * @see guru.merlin.core.EnvelopeHandler#commitHandEnvelope(java.lang.Object)
     */
    public void commitHandEnvelope(Object EnvelopeReference) throws EPException {
        //Aqui deberían estar los "commit" que actualmente están en el handEnvelope.
        //Para cambiarlos de lugar se requiere asegurar que el Bus tambien sea transaccional y
        //asegurar la cadena completa.
    }
    
    public void callGarbageCollector(){
        if (calendarControl==null){
            calendarControl = java.util.Calendar.getInstance();
            minControl= calendarControl.get(java.util.Calendar.MINUTE);
        } else {
            calendarTmp = java.util.Calendar.getInstance();
            minTmp = calendarTmp.get(java.util.Calendar.MINUTE);
        }
        
        if (calendarTmp!=null&&calendarControl!=null){
            if ((minTmp-minControl)>(1)) {
                calendarControl=null;
                System.gc();
            }
        }
    }
    
    private void startNodeConnectionSentry(){//MOD SANTIAGO
        try {
            ncs = new NodeConnectionSentry(this);
            ncs.start();
        } catch (Exception e){
            e.printStackTrace();
            logger.info("",e);
        }
    }
    
    private void printStackTraceByLogger(Throwable e){
        StringBuffer sb = new StringBuffer();
        StackTraceElement stt[] = e.getStackTrace();
        sb.append("Se genero un: " + e + "\n");
        sb.append("Stacktrace:\n");
        for (int d = 0; d < stt.length; d++) {
            sb.append(stt[d].toString() + "\n");
        }
        logger.error(sb.toString());
    }
}